{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 环境参数更新\n",
    "\n",
    "不建议运行，消耗时间很大\n",
    "\n",
    "> 通过学习与实践，我们可以看出模型预测控制（MPC）方法有着其独特的优势，例如它不用构建和训练策略，可以更好地利用环境，可以进行更长步数的规划。但是 MPC 也有其局限性，例如模型在多步推演之后的准确性会大大降低，简单的控制策略对于复杂系统可能不够。MPC 还有一个更为严重的问题，即每次计算动作的复杂度太大，这使其在一些策略及时性要求较高的系统中应用就变得不太现实。\n",
    "\n",
    "对于环境P, 其参数还是state和action, 每次传入一个s和a, 都会更新一次环境分布的mean和std"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "notebookRunGroups": {
     "groupValue": ""
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from scipy.stats import truncnorm\n",
    "import gymnasium as gym\n",
    "import itertools\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import collections\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "\n",
    "class CEM:\n",
    "    def __init__(self, n_sequence, elite_ratio, fake_env, upper_bound, lower_bound):\n",
    "        self.n_sequence = n_sequence\n",
    "        self.elite_ratio = elite_ratio\n",
    "        self.upper_bound = upper_bound\n",
    "        self.lower_bound = lower_bound\n",
    "        self.fake_env = fake_env\n",
    "\n",
    "    def optimize(self, state, init_mean, init_var):\n",
    "        '''输入状态，动作序列是截断正态分布生成的，保留回报比较高的动作序列，返回动作均值\\\\\n",
    "            要注意的是，这里的回报是从`fake_env`得到的'''\n",
    "        mean, var = init_mean, init_var\n",
    "        # 截断x在-2到2的标准正态分布\n",
    "        X = truncnorm(-2, 2, loc=np.zeros_like(mean), scale=np.ones_like(var))\n",
    "        state = np.tile(state, (self.n_sequence, 1))  # 把state在当前维度复制n份\n",
    "\n",
    "        for _ in range(5):\n",
    "            # 约束和均值的差值\n",
    "            lb_dist, ub_dist = mean - self.lower_bound, self.upper_bound - mean\n",
    "            # 取约束范围和原本方差之间的最小值\n",
    "            constrained_var = np.minimum(np.minimum(np.square(lb_dist / 2), np.square(ub_dist / 2)), var)\n",
    "            # 生成动作序列, X.rvs()是抽取的样本, 乘以标准差加上均值还原动作, 返回一个动作序列\n",
    "            action_sequences = [X.rvs() for _ in range(self.n_sequence)] * np.sqrt(constrained_var) + mean\n",
    "            # 使用模拟环境fake_env评估每个动作序列的累积奖励，并保存在returns数组中\n",
    "            returns = self.fake_env.propagate(state, action_sequences)[:, 0]\n",
    "            # 选取累积奖励最高的若干条动作序列, ndarray[ndarray]可以按照索引重新排序, 取前面一定比例作为精英序列\n",
    "            elites = action_sequences[np.argsort(returns)][-int(self.elite_ratio * self.n_sequence):]\n",
    "            new_mean = np.mean(elites, axis=0)\n",
    "            new_var = np.var(elites, axis=0)\n",
    "            # 更新动作序列分布, 用于下一次迭代\n",
    "            mean = 0.1 * mean + 0.9 * new_mean\n",
    "            var = 0.1 * var + 0.9 * new_var\n",
    "        # 返回的是动作序列的均值\n",
    "        return mean"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 权重初始化和集成的单全连接层\n",
    "\n",
    "ensemble_size是集成数量， 这里构建的是单层全连接，集成可以看做是平行不相关的若干全连接层."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "device = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "\n",
    "\n",
    "class Swish(nn.Module):\n",
    "    ''' Swish激活函数 '''\n",
    "\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "\n",
    "    def forward(self, x):\n",
    "        return x * torch.sigmoid(x)\n",
    "\n",
    "\n",
    "def init_weights(m):\n",
    "    ''' 初始化模型权重 '''\n",
    "    def truncated_normal_init(t, mean=0.0, std=0.01):\n",
    "        torch.nn.init.normal_(t, mean=mean, std=std)\n",
    "        while True:\n",
    "            # 各t是在均值附近两个标准差以外则为1\n",
    "            cond = (t < mean - 2 * std) | (t > mean + 2 * std)\n",
    "            # 如果cond都是0(满足上述条件), break, 否则对超出的t重抽, 这是为了防止mean和std更新太大\n",
    "            if not torch.sum(cond):\n",
    "                break\n",
    "            # cond作为condition, True的部分重抽, False的部分保留, 还是t对应的值\n",
    "            t = torch.where(\n",
    "                cond,\n",
    "                torch.nn.init.normal_(torch.ones(t.shape, device=device),\n",
    "                                      mean=mean,\n",
    "                                      std=std),\n",
    "                t)\n",
    "        return t\n",
    "\n",
    "    if type(m) == nn.Linear or isinstance(m, FCLayer):\n",
    "        # 初始化权重\n",
    "        truncated_normal_init(m.weight, std=1 / (2 * np.sqrt(m._input_dim)))\n",
    "        m.bias.data.fill_(0.0)  # 偏置填充0\n",
    "\n",
    "\n",
    "class FCLayer(nn.Module):\n",
    "    ''' 集成之后的全连接层 '''\n",
    "\n",
    "    def __init__(self, input_dim, output_dim, ensemble_size, activation):\n",
    "        super().__init__()\n",
    "        self._input_dim, self._output_dim = input_dim, output_dim\n",
    "        self.weight = nn.Parameter(torch.Tensor(ensemble_size, input_dim, output_dim).to(device))\n",
    "        self._activation = activation\n",
    "        self.bias = nn.Parameter(torch.Tensor(ensemble_size, output_dim).to(device))\n",
    "\n",
    "    def forward(self, x):\n",
    "        # bmm矩阵乘法, add相加, 实际上是网络中的单层(全连接)计算, 但是同时也定义了输入和输出维度\n",
    "        return self._activation(\n",
    "            torch.add(torch.bmm(x, self.weight), self.bias[:, None, :]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 定义集成环境模型\n",
    "\n",
    "也就是构建网络, 损失函数, 训练（梯度下降）函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "class EnsembleModel(nn.Module):\n",
    "    ''' 环境模型集成 '''\n",
    "\n",
    "    def __init__(self,\n",
    "                 state_dim,\n",
    "                 action_dim,\n",
    "                 ensemble_size=5,\n",
    "                 learning_rate=1e-3):\n",
    "        super(EnsembleModel, self).__init__()\n",
    "        # ? 输出包括均值和方差, 因此是状态与奖励维度之和的两倍\n",
    "        self._output_dim = (state_dim + 1) * 2\n",
    "        self._max_logvar = nn.Parameter(\n",
    "            (torch.ones((1, self._output_dim // 2)).float() / 2).to(device),\n",
    "            requires_grad=False)\n",
    "        self._min_logvar = nn.Parameter(\n",
    "            (-torch.ones((1, self._output_dim // 2)).float() * 10).to(device),\n",
    "            requires_grad=False)\n",
    "\n",
    "        # 定义若干集成的全连接层\n",
    "        self.layer1 = FCLayer(state_dim + action_dim, 200, ensemble_size, Swish())\n",
    "        self.layer2 = FCLayer(200, 200, ensemble_size, Swish())\n",
    "        self.layer3 = FCLayer(200, 200, ensemble_size, Swish())\n",
    "        self.layer4 = FCLayer(200, 200, ensemble_size, Swish())\n",
    "        self.layer5 = FCLayer(200, self._output_dim, ensemble_size, nn.Identity())\n",
    "        self.apply(init_weights)  # 初始化环境模型中的参数\n",
    "        self.optimizer = torch.optim.Adam(self.parameters(), lr=learning_rate)\n",
    "\n",
    "    def forward(self, x, return_log_var=False):\n",
    "        # * 网络计算, 需要注意的是, 这里网络输出的是奖励\n",
    "        ret = self.layer5(self.layer4(self.layer3(self.layer2(self.layer1(x)))))\n",
    "        mean = ret[:, :, :self._output_dim // 2]\n",
    "        # 在PETS算法中, 将方差控制在最小值和最大值之间\n",
    "        logvar = self._max_logvar - F.softplus(self._max_logvar - ret[:, :, self._output_dim // 2:])\n",
    "        logvar = self._min_logvar + F.softplus(logvar - self._min_logvar)\n",
    "        return mean, logvar if return_log_var else torch.exp(logvar)\n",
    "\n",
    "    def loss(self, mean, logvar, labels, use_var_loss=True):\n",
    "        inverse_var = torch.exp(-logvar)\n",
    "        if use_var_loss:\n",
    "            mse_loss = torch.mean(torch.mean(torch.pow(mean - labels, 2) *\n",
    "                                             inverse_var,\n",
    "                                             dim=-1),\n",
    "                                  dim=-1)\n",
    "            var_loss = torch.mean(torch.mean(logvar, dim=-1), dim=-1)\n",
    "            total_loss = torch.sum(mse_loss) + torch.sum(var_loss)\n",
    "        else:\n",
    "            mse_loss = torch.mean(torch.pow(mean - labels, 2), dim=(1, 2))\n",
    "            total_loss = torch.sum(mse_loss)\n",
    "        return total_loss, mse_loss\n",
    "\n",
    "    def train(self, loss):\n",
    "        self.optimizer.zero_grad()\n",
    "        loss += 0.01 * torch.sum(self._max_logvar) - 0.01 * torch.sum(self._min_logvar)\n",
    "        loss.backward()\n",
    "        self.optimizer.step()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 精细化训练函数\n",
    "\n",
    "接下来，我们定义一个`EnsembleDynamicsModel`的类，把模型集成的训练设计得更加精细化。具体而言，我们并不会选择模型训练的轮数，而是在每次训练的时候将一部分数据单独取出来，用于验证模型的表现，在 5 次没有获得表现提升时就结束训练。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "class EnsembleDynamicsModel:\n",
    "    ''' 环境模型集成, 加入精细化的训练 '''\n",
    "\n",
    "    def __init__(self, state_dim, action_dim, num_network=5):\n",
    "        self._num_network = num_network\n",
    "        self._state_dim, self._action_dim = state_dim, action_dim\n",
    "        self.model = EnsembleModel(state_dim,\n",
    "                                   action_dim,\n",
    "                                   ensemble_size=num_network)\n",
    "        self._epoch_since_last_update = 0\n",
    "\n",
    "    def train(self,\n",
    "              inputs,\n",
    "              labels,\n",
    "              batch_size=64,\n",
    "              holdout_ratio=0.1,\n",
    "              max_iter=20):\n",
    "        # 设置训练集与验证集\n",
    "        permutation = np.random.permutation(inputs.shape[0])  # 随机生成序号\n",
    "        inputs, labels = inputs[permutation], labels[permutation]\n",
    "        num_holdout = int(inputs.shape[0] * holdout_ratio)  # 验证集数量\n",
    "        # 训练集部分\n",
    "        train_inputs, train_labels = inputs[num_holdout:], labels[num_holdout:]\n",
    "        # 验证集部分\n",
    "        holdout_inputs, holdout_labels = inputs[:num_holdout], labels[:num_holdout]\n",
    "        holdout_inputs = torch.from_numpy(holdout_inputs).float().to(device)\n",
    "        holdout_labels = torch.from_numpy(holdout_labels).float().to(device)\n",
    "        # 在新增的维度上复制_num_network次, 即要给_num_network个网络训练\n",
    "        holdout_inputs = holdout_inputs[None, :, :].repeat([self._num_network, 1, 1])\n",
    "        holdout_labels = holdout_labels[None, :, :].repeat([self._num_network, 1, 1])\n",
    "\n",
    "        # 保留最好的结果\n",
    "        self._snapshots = {i: (None, 1e10) for i in range(self._num_network)}\n",
    "\n",
    "        for epoch in itertools.count():\n",
    "            # 定义每一个网络的训练数据\n",
    "            train_index = np.vstack([\n",
    "                np.random.permutation(train_inputs.shape[0])\n",
    "                for _ in range(self._num_network)\n",
    "            ])\n",
    "            # 所有真实数据都用来训练\n",
    "            for batch_start_pos in range(0, train_inputs.shape[0], batch_size):\n",
    "                batch_index = train_index[:, batch_start_pos:batch_start_pos + batch_size]\n",
    "                train_input = torch.from_numpy(train_inputs[batch_index]).float().to(device)\n",
    "                train_label = torch.from_numpy(train_labels[batch_index]).float().to(device)\n",
    "\n",
    "                mean, logvar = self.model(train_input, return_log_var=True)\n",
    "                loss, _ = self.model.loss(mean, logvar, train_label)\n",
    "                self.model.train(loss)\n",
    "\n",
    "            # 验证集\n",
    "            with torch.no_grad():\n",
    "                mean, logvar = self.model(holdout_inputs, return_log_var=True)\n",
    "                _, holdout_losses = self.model.loss(mean, logvar, holdout_labels, use_var_loss=False)\n",
    "                holdout_losses = holdout_losses.cpu()\n",
    "                break_condition = self._save_best(epoch, holdout_losses)\n",
    "                if break_condition or epoch > max_iter:  # 结束训练\n",
    "                    break\n",
    "\n",
    "    def _save_best(self, epoch, losses, threshold=0.1):\n",
    "        '''更新最小损失'''\n",
    "        updated = False\n",
    "        for i in range(len(losses)):\n",
    "            current = losses[i]\n",
    "            _, best = self._snapshots[i]\n",
    "            improvement = (best - current) / best\n",
    "            if improvement > threshold:\n",
    "                self._snapshots[i] = (epoch, current)\n",
    "                updated = True\n",
    "        self._epoch_since_last_update = 0 if updated else self._epoch_since_last_update + 1\n",
    "        return self._epoch_since_last_update > 5\n",
    "\n",
    "    def predict(self, inputs, batch_size=64):\n",
    "        '''预测'''\n",
    "        mean, var = [], []\n",
    "        for i in range(0, inputs.shape[0], batch_size):\n",
    "            input = torch.from_numpy(inputs[i:min(i + batch_size, inputs.shape[0])]).float().to(device)\n",
    "            cur_mean, cur_var = self.model(input[None, :, :].repeat([self._num_network, 1, 1]), return_log_var=False)\n",
    "            mean.append(cur_mean.detach().cpu().numpy())\n",
    "            var.append(cur_var.detach().cpu().numpy())\n",
    "        return np.hstack(mean), np.hstack(var)  # 展平"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 环境接口\n",
    "\n",
    "有了环境模型之后，我们就可以定义一个`FakeEnv`，定义`step`，传入obs（state）和action，输出rewards和next_obs。该功能会用在 MPC 算法中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "class FakeEnv:\n",
    "    def __init__(self, model):\n",
    "        self.model = model\n",
    "\n",
    "    def step(self, obs, act):\n",
    "        '''采取动作，返回rewards和next_obs'''\n",
    "        inputs = np.concatenate((obs, act), axis=-1)\n",
    "        ensemble_model_means, ensemble_model_vars = self.model.predict(inputs)\n",
    "        ensemble_model_means[:, :, 1:] += obs.numpy()\n",
    "        ensemble_model_stds = np.sqrt(ensemble_model_vars)\n",
    "        ensemble_samples = (\n",
    "            ensemble_model_means + np.random.normal(size=ensemble_model_means.shape) * ensemble_model_stds\n",
    "        )\n",
    "\n",
    "        num_models, batch_size, _ = ensemble_model_means.shape\n",
    "        models_to_use = np.random.choice(\n",
    "            [i for i in range(self.model._num_network)], size=batch_size\n",
    "        )\n",
    "        batch_inds = np.arange(0, batch_size)\n",
    "        samples = ensemble_samples[models_to_use, batch_inds]\n",
    "        rewards, next_obs = samples[:, :1], samples[:, 1:]\n",
    "        return rewards, next_obs\n",
    "\n",
    "    def propagate(self, obs, actions):\n",
    "        with torch.no_grad():\n",
    "            obs = np.copy(obs)\n",
    "            total_reward = np.expand_dims(np.zeros(obs.shape[0]), axis=-1)\n",
    "            obs, actions = torch.as_tensor(obs), torch.as_tensor(actions)\n",
    "            for i in range(actions.shape[1]):\n",
    "                action = torch.unsqueeze(actions[:, i], 1)\n",
    "                rewards, next_obs = self.step(obs, action)\n",
    "                total_reward += rewards\n",
    "                obs = torch.as_tensor(next_obs)\n",
    "            return total_reward"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 经验池"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ReplayBuffer:\n",
    "    def __init__(self, capacity):\n",
    "        self.buffer = collections.deque(maxlen=capacity)\n",
    "\n",
    "    def add(self, state, action, reward, next_state, done, truncated):\n",
    "        self.buffer.append((state, action, reward, next_state, done, truncated))\n",
    "\n",
    "    def size(self):\n",
    "        return len(self.buffer)\n",
    "\n",
    "    def return_all_samples(self):\n",
    "        all_transitions = list(self.buffer)\n",
    "        state, action, reward, next_state, done, truncated = zip(*all_transitions)\n",
    "        return np.array(state), action, reward, np.array(next_state), done, truncated"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# PETS算法\n",
    "\n",
    "带有轨迹采样的概率集成（probabilistic ensembles with trajectory sampling，PETS）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "class PETS:\n",
    "    ''' PETS算法 '''\n",
    "\n",
    "    def __init__(self, env, replay_buffer, n_sequence, elite_ratio, plan_horizon, num_episodes):\n",
    "        self._env = env\n",
    "        self._env_pool = replay_buffer\n",
    "\n",
    "        obs_dim = env.observation_space.shape[0]\n",
    "        self._action_dim = env.action_space.shape[0]\n",
    "        self._model = EnsembleDynamicsModel(obs_dim, self._action_dim)\n",
    "        self._fake_env = FakeEnv(self._model)\n",
    "        self.upper_bound = env.action_space.high[0]\n",
    "        self.lower_bound = env.action_space.low[0]\n",
    "\n",
    "        self._cem = CEM(n_sequence, elite_ratio, self._fake_env, self.upper_bound, self.lower_bound)\n",
    "        self.plan_horizon = plan_horizon  # 往后预测几步\n",
    "        self.num_episodes = num_episodes\n",
    "\n",
    "    def train_model(self):\n",
    "        env_samples = self._env_pool.return_all_samples()\n",
    "        obs = env_samples[0]\n",
    "        actions = np.array(env_samples[1])\n",
    "        rewards = np.array(env_samples[2]).reshape(-1, 1)\n",
    "        next_obs = env_samples[3]\n",
    "        inputs = np.concatenate((obs, actions), axis=-1)\n",
    "        labels = np.concatenate((rewards, next_obs - obs), axis=-1) # ? 为什么是 next_obs - obs\n",
    "        self._model.train(inputs, labels)\n",
    "\n",
    "    def mpc(self):\n",
    "        # np.tile, 在对应维度复制若干数组\n",
    "        mean = np.tile((self.upper_bound + self.lower_bound) / 2.0, self.plan_horizon)\n",
    "        var = np.tile(np.square(self.upper_bound - self.lower_bound) / 16, self.plan_horizon)\n",
    "        obs, done, truncated, episode_return = self._env.reset()[0], False, False, 0\n",
    "        while not (done | truncated):\n",
    "            actions = self._cem.optimize(obs, mean, var)  # 返回动作序列的均值\n",
    "            action = actions[:self._action_dim]\n",
    "            next_obs, reward, done, truncated, _ = self._env.step(action)\n",
    "            self._env_pool.add(obs, action, reward, next_obs, done, truncated)\n",
    "            obs = next_obs\n",
    "            episode_return += reward\n",
    "            # np.concatenate([a, b]) 横向拼接\n",
    "            mean = np.concatenate([np.copy(actions)[self._action_dim:], np.zeros(self._action_dim)])\n",
    "        return episode_return\n",
    "\n",
    "    def explore(self):\n",
    "        '''随机采取动作探索'''\n",
    "        obs, done, truncated, episode_return = self._env.reset()[0], False, False, 0\n",
    "        while not (done | truncated):\n",
    "            action = self._env.action_space.sample()  # 随机采样动作\n",
    "            next_obs, reward, done, truncated, _ = self._env.step(action)\n",
    "            self._env_pool.add(obs, action, reward, next_obs, done, truncated)\n",
    "            obs = next_obs\n",
    "            episode_return += reward\n",
    "        return episode_return\n",
    "\n",
    "    def train(self):\n",
    "        return_list = []\n",
    "        explore_return = self.explore()  # 先进行随机策略的探索来收集一条序列的数据\n",
    "        print('episode: 1, return: %d' % explore_return)\n",
    "        return_list.append(explore_return)\n",
    "\n",
    "        for i_episode in range(self.num_episodes - 1):\n",
    "            self.train_model()\n",
    "            episode_return = self.mpc()\n",
    "            return_list.append(episode_return)\n",
    "            print('episode: %d, return: %d' % (i_episode + 2, episode_return))\n",
    "        return return_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "episode: 1, return: -954\n",
      "episode: 2, return: -1068\n",
      "episode: 3, return: -1392\n",
      "episode: 4, return: -1007\n",
      "episode: 5, return: -125\n",
      "episode: 6, return: -492\n",
      "episode: 7, return: -125\n",
      "episode: 8, return: -363\n",
      "episode: 9, return: -124\n",
      "episode: 10, return: -126\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlAAAAHJCAYAAABKYwdTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABwa0lEQVR4nO3dd3xUVd7H8c9MyqRDAqTQ0ui9hBJpKsKCWAAfH9SVdXdBWRFlV10L4oqLuM8uWCmujV10V9dKkCZrRcFIR3oxoSSQAiEkIWWSzMzzR8hIDCX9ziTf9+vlS+beO3d+k5Nkvjn33HNMDofDgYiIiIhUmdnoAkRERETcjQKUiIiISDUpQImIiIhUkwKUiIiISDUpQImIiIhUkwKUiIiISDUpQImIiIhUkwKUiIiISDUpQImIiIhUk6fRBYhIw5g8eTKbN2+usM3Ly4uWLVtyzTXX8Pvf/55mzZoBsHDhQhYtWnTZ823fvp1169bx+OOPX/G1v/jiC9q2bUtpaSn/+te/WLFiBUeOHMFkMhEVFcW4ceP41a9+hbe3d83fYC18/PHHld6H2WwmICCAnj17ct9999G/f/8GraX8a1YVqampjBw5kr/85S9MnDixniusG1988QXTp0/n4MGDRpciUiMKUCJNSLdu3Xjqqaecj0tKSti7dy/PP/88+/fv591338VkMjn3v/fee5c8l6+vL1dffXWFY77++mteeeUVFi1aRKtWrZzbQ0NDAXjyySdZt24dd999Nz179sThcLB161Zefvlltm/fzpIlS+ry7VbbhXXb7XZOnz7N4sWLueuuu/jwww/p0qWLofU1FomJiTz88MNGlyFSKwpQIk1IQEAAffr0qbBtwIAB5Ofn8/LLL/PDDz9U2P/zY38uJCSEkJAQ5+Pk5GQAunbtWqn35OTJkyxfvpynn36aSZMmObcPGzaMkJAQnn32WXbt2kWvXr1q9ubqwMXq7tatG6NGjeKdd97hz3/+s0GVNQ7nzp3j1Vdf5c033yQwMJCCggKjSxKpMY2BEhF69OgBlIWc+nL69GkcDgcXW7/8xhtv5MEHHyQoKOiy59i4cSN33HEH/fv3Z9CgQTz00EOkpaU593/88cd069aNH374gUmTJtGzZ0+uvvpqXn/99RrX3bZtW4KDgyt8bU6ePMmDDz7IwIED6d27N3fddRf79u1z7k9NTaVz586sXbuWBx54gL59+zJgwACeeOIJ8vPzncfZ7XaWLFnC1VdfTe/evZk+fTo5OTkVXn/hwoV07ty5Ul2dO3dm4cKFF635448/pnPnzqSmplbYfu211/LYY49VOMe7777LY489Rv/+/Rk4cCDPPPMMRUVF/PWvf2Xw4MEMGjSIJ554AqvVesmv0fbt2+ncuTOff/55he1JSUnOrwPAhx9+yIcffsif/vQn7rzzzkueT8QdKECJCEeOHAGgXbt2FbaXlpZe9D+73V7t1+jSpQsRERH85S9/4emnn+abb77h3LlzQFlP1rRp04iKirrk81esWMFvf/tbwsLCeP7553n88cfZsWMHkyZNIisry3mc3W7n97//Pddffz2vvfYa/fv3Z8GCBXz77bfVrhkgOzub7Oxs2rdvD8CZM2e47bbb2Lt3L08++STPPfccdrudX/7ylyQlJVV47lNPPUWbNm1YsmQJU6dO5aOPPuLvf/+7c//8+fNZvHgxt9xyC4sWLSI4OJjnnnuuRnXW1IIFC/D29mbRokXcfPPNvP3224wfP560tDTmz5/Pbbfdxocffsjbb799yXP069ePyMhI1qxZU2H7ypUrCQwM5NprrwXKAtyXX37JbbfdVq/vSaQh6BKeSBPicDgoLS11Ps7JyWHz5s288sor9OnTx9kTVa579+4XPc///M//MG/evGq9tre3N6+99hqPPPII77zzDu+88w5ms5nu3bszZswY7rzzTnx8fC76XLvdzvz587nqqqt44YUXnNv79evH9ddfz9KlS/njH//ofI/Tp0/n1ltvBaB///589tlnfP311wwbNuyyNdrtdufXx2q1cuzYMebPn4/ZbHZedly2bBlnz57l3XffpU2bNgAMHz6c66+/npdeeomXX37Zeb4RI0bw6KOPAhAfH8/GjRv5+uuveeihh8jNzeXtt9/mV7/6Fffffz9QdjkzIyOjxmGvJmJjY52XJgcMGMCHH35ISUkJCxYswNPTk2HDhvHll1+yffv2y57npptu4s0336SwsBBfX18AVq9ezZgxY7BYLADOECrSGChAiTQhW7ZsqRSKzGYz8fHxzJ07t8IAcii75HIxF457qo5OnTqRkJDA7t272bBhA5s2bWLHjh3s3r2bDz/8kHfeeeei5z5y5AinTp3iwQcfrLC9ffv29O3bl02bNlXY3rdv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r3/9q1KPT02FhYVddmmY48ePM2/ePL788ktat24NwIsvvsjYsWN58803mTVrFhaLheLi4grPK3/s6+uLj49Ppf1Qdmeer2/tUnppaf18g9ps9no7t1RfU2qPpNQc7A4Hzfy9CfLzctn33ZTaxB2oPVyL2qP2anQRdMmSJaxcuZJnnnmGjRs34nA4gLKFhO12Oy+88EKdFejl5UVsbOwl/9uxYwctWrRwhqfy53Tr1o2jR48CEBERQWZmZoXzZmZm4ufnR2BgIOHh4Zw9e7ZSiMrMzKyzICjSWFw4/5PJZDK4GhERY9QoQH300Uc88MAD3HLLLTRv3ty5vUuXLjzwwANs3Lixruq7ooiICLKzsysEJLvdzo8//ugcRB4XF8fmzZsrPC8xMZF+/fphNpvp378/drvdOZgcIDk5mYyMDOddeSJSpnwGck2gKSJNWY0C1OnTp+natetF94WFhZGbm1uroqrjmmuuoV27djzwwAP88MMPJCUl8eSTT5KWlsavfvUrACZPnsyuXbtYsGABSUlJLF26lHXr1jF16lRnzePGjWP27Nls2rSJXbt28dBDDzFw4EBNYSDyM0dOagJNEZEaBajIyEjWr19/0X2bN2929vw0BD8/P9566y3atGnDfffdx2233UZaWhrvvvsu7dq1A6Bjx44sWbKE9evXM378eD744APmz59PfHy88zxz584lPj6eGTNmMGXKFGJiYnj55Zcb7H2IuIOc/GKycoswAVEKUCLShNVoEPldd93Fn/70J0pKSrjmmmswmUwcO3aMTZs2sXTpUh577LG6rvOywsLCeO655y57zPDhwxk+fPgl9/v5+fHMM8/wzDPP1HV5Io1G+QSaES398bXU6NeHiEijUKPfgLfeeitnzpzh73//O++++y4Oh4MHH3wQLy8vpk6dyu23317XdYqIC0jW5TsREaCGAQpg2rRp/PKXv2THjh2cPXuWoKAgevfuTWBgIG+99ZZz/JGINB4X3oEnItKUVStAbdiwgY8++giA8ePHM2LECIYNG+bcv2XLFubOncvhw4cVoEQaGbvDwdF0BSgREahGgFqzZg0PPvgg3t7eeHl58emnn/Lyyy8zatQosrOzmTdvHqtXr8bDw4Pf/OY39VmziBggPauAQqsNby8zbVq55vItIiINpcoB6p///Ce9e/fmzTffxNvbm9mzZ7N4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EwcOHCA/P5/Bgwc7twUFBdGtWze2bNliYGVNU3BwMK+99ho9evRwbjOZTDgcDnJycgysrGnbsmUL7733Hn/961+NLqVJS05O5sSJE9x4440Vtr/55ptMmzbNoKqatubNm/PVV1+RmpqKzWbjvffew9vbm65duxpdmtvyNLoAqZr09HQAIiIiKmwPDQ0lLS3NiJKatKCgIEaMGFFh29q1azl+/DhDhw41qKqmLTc3l0ceeYTZs2dX+jmRhnX06FEACgoKmDJlCvv27aNt27bce++9XHvttcYW10Q98cQT/OEPf2DkyJF4eHhgNpt56aWXaN++vdGluS31QLmJwsJCALy9vStst1gsWK1WI0qSC2zbto1Zs2YxcuRIfUAYZM6cOfTp06dSr4c0vHPnzgHw6KOPcsMNN7B06VKGDBnC9OnTSUxMNLi6pikpKYmgoCAWL17Me++9x8SJE3n00Uc5cOCA0aW5LfVAuQkfHx+gbCxU+b8BrFYrvr6+RpUlwOeff87DDz9M7969ef75540up0lKSEhg69atrFy50uhSBPDy8gJgypQpTJgwAYCuXbuyb98+/vGPfxAfH29keU3OiRMn+OMf/8g///lP4uLiAOjZsyc//vgjCxcuZPHixQZX6J7UA+Umyi9JZGZmVtiemZlJeHi4ESUJ8K9//Yv777+f4cOH8/rrr1cIt9JwPvroI7Kyspy3aPft2xeAp556inHjxhlcXdNT/jupU6dOFbZ36NCB1NRUI0pq0nbt2kVJSQk9e/assL13797Oy61SfeqBchNdunQhICCATZs2Oa9Z5+bmsm/fPu68806Dq2ua3nnnHebOncvkyZOZNWsWZrP+HjHKggULKCoqqrBt9OjRPPDAA1x//fUGVdV0devWDX9/f3744QdnjwfAoUOHNObGAOV/gB88eJBevXo5tx86dIjIyEijynJ7ClBuwtvbmzvvvJMFCxYQEhJCmzZtmD9/PuHh4YwaNcro8pqcI0eO8OyzzzJq1CimTZtGVlaWc5+Pjw+BgYEGVtf0hIWFXXR7ixYtaNOmTQNXIz4+PkydOpXFixcTFhZGr169WL16NRs3buSf//yn0eU1Ob169SIuLo5HH32Up556ivDwcBISEkhMTOSdd94xujy3pQDlRh544AFKS0uZPXs2RUVFDBgwgDfffLPSwHKpf+vWraOkpITPPvus0jxcEyZM4P/+7/8MqkzENUyfPh1fX1/nfHWxsbEsXLiQQYMGGV1ak2M2m1myZAkvvvgijz/+ODk5OXTq1Il//vOf9OnTx+jy3JbJ4XA4jC5CRERExJ1o0IaIiIhINSlAiYiIiFSTApSIiIhINSlAiYiIiFSTApSIiIhINSlAiYiIiFSTApSIiIhINSlAiYiIiFSTZiIXEbfz2GOPsXz58kvub968OZs2barSuRYuXMiiRYs4ePBgXZV3SZMnTwbg7bffrvfXEpH6pQAlIm6pVatWLFq06KL7PD2r/qvt1ltvZdiwYXVVlog0EQpQIuKWvL2962Qdr/DwcMLDw2tfkIg0KRoDJSKN1uTJk3nsscd49dVXGTJkCP369ePee+8lJSXFeczChQvp3Lmz83FKSgr33nsvgwYNonfv3kyaNIn169dXOO/u3buZMmUKgwYNol+/fvzud7/j8OHDFY45efIkM2bMoH///gwZMoR//OMfF63xgw8+YNy4cfTo0YOrr76ahQsXUlpa6tx/5swZHn74YYYMGULPnj25+eabSUhIqIOvjojUhnqgRMRtXRg0LuTh4YHJZALgiy++IDg4mCeeeAK73c5zzz3Hr371K1avXo2fn1+F59ntdqZNm0arVq3429/+hqenJ2+99RbTp09nzZo1REZG8v333zN16lQGDBjAvHnzKC4u5tVXX+W2227j/fffJzY2loKCAu68807MZjN//vOf8fT05KWXXuL48eP07dvX+XqvvvoqL7zwAnfeeSePP/44+/fvZ+HChaSlpfHss88C8Mc//pGsrCyefvpp/P39+eSTT3j00UeJiIhg0KBB9fSVFZErUYASEbd04sQJunfvftF9M2fOZPr06QAUFBTw0Ucf0b59ewBiYmKYMGECy5cv55e//GWF52VlZZGUlMTvfvc7RowYAUCvXr1YtGgRVqsVgOeee4527drxxhtv4OHhAcDQoUMZNWoUCxcu5MUXX2T58uWcPHmSFStWOHu3evXqxahRo5yvlZeXxyuvvMKkSZOYPXu28zzNmzdn9uzZ/OY3v6Fjx45s3ryZ6dOnc9111wEwaNAgmjdv7nxtETGGApSIuKVWrVrxyiuvXHRfWFiY8999+/Z1hieAbt260a5dO7Zu3VopQLVs2ZIOHTrw5JNP8t133zF8+HCGDh3K448/DpSFsd27d3PfffdVCDBBQUFcc801zkt9W7dupV27dhUuDUZERFQYs7Vjxw4KCwu59tprK/SkXXvttQBs3LiRjh07MmjQIBYuXMiBAwcYMWIEw4cP59FHH63ul0tE6pgClIi4JW9vb3r27HnF40JDQytta9GiBbm5uZW2m0wmli5dyiuvvMJnn33G8uXL8fLy4rrrrmPOnDlYrVYcDgctW7as9NyWLVuSl5cHQE5ODiEhIZWOadWqFadPnwbg7NmzANxzzz0XrTszMxOAF154gb///e+sXbuWTz/9FLPZzFVXXcWcOXNo167dFd+/iNQPBSgRadTKg8qFTp8+XaFX6kJhYWHMmTOHp556igMHDvDpp5/y+uuv06xZMx599FFMJpMzBF3o1KlTNG/eHIDg4GCOHTt22VqCgoIAWLBgAVFRUZWOLQ9pgYGB/PGPf+SPf/wjycnJfPHFFyxZsoSnn36aN9544wrvXkTqi+7CE5FGbceOHZw5c8b5eO/evaSmphIfH3/RY6+66ip27dqFyWSia9eu/OEPf6BTp06kp6fj5+dHjx49WLNmDTabzfm8vLw8vv76a/r37w/A4MGDSU1NZffu3c5jzpw5w86dO52Pe/fujZeXFxkZGfTs2dP5n5eXF8899xypqamcOHGCESNG8OmnnwJl47fuvvturrrqKtLT0+v6SyUi1aAeKBFxS8XFxRUCyc916tQJgMLCQu6++27uvfde8vPzeeGFF+jUqRM33HBDped069YNHx8fHnnkEe6//35atmzJd999x/79+/nVr34FwEMPPcSUKVOYOnUqd955JyUlJbz22msUFxczY8YMAG6++WbeeustZsyYwR/+8AcCAgJ45ZVXsNvtztcKDg5m6tSpvPTSS5w7d45BgwaRkZHBSy+9hMlkokuXLgQGBhIeHs4zzzzDuXPnaN++PXv27GH9+vVMmzatDr+aIlJdJofD4TC6CBGR6rjSUi4AH374IX/7299wOBwMHjzYuXzKtddeyyOPPEJwcDBQeSmXo0eP8txzz7Ft2zZyc3OJiopi8uTJTJo0yXnuTZs28fLLL7Nnzx68vb2Ji4vjwQcfpGPHjs5jzpw5w7PPPsv69esxmUz87//+L6mpqWRlZVVYyuXf//4377zzDseOHaNZs2bEx8fz4IMP0rp1a6Ds0uDzzz/Phg0byM7OJiIigltuuYV77rkHs1kXEUSMogAlIo2W1p4TkfqiP19EREREqkkBSkRERKSadAlPREREpJrUAyUiIiJSTQpQIiIiItWkACUiIiJSTQpQIiIiItWkACUiIiJSTQpQIiIiItWkACUiIiJSTQpQIiIiItX0/4wT61G9M1MyAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "buffer_size = 100000\n",
    "n_sequence = 50\n",
    "elite_ratio = 0.2\n",
    "plan_horizon = 25\n",
    "num_episodes = 10\n",
    "env_name = \"Pendulum-v1\"\n",
    "env = gym.make(env_name)\n",
    "\n",
    "replay_buffer = ReplayBuffer(buffer_size)\n",
    "pets = PETS(env, replay_buffer, n_sequence,\n",
    "            elite_ratio, plan_horizon, num_episodes)\n",
    "return_list = pets.train()\n",
    "\n",
    "episodes_list = list(range(len(return_list)))\n",
    "sns.set()\n",
    "plt.plot(episodes_list, return_list)\n",
    "plt.xlabel(\"Episodes\")\n",
    "plt.ylabel(\"Returns\")\n",
    "plt.title(\"PETS on {}\".format(env_name))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 算法框架图\n",
    "\n",
    "第一次见这么复杂的。。。并没有完全搞清楚\n",
    "\n",
    "![PETS算法.jpg](https://s2.loli.net/2023/08/28/TPfkbxCWD5UH7ZA.jpg)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.5"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
